/**
 * 时间：2024.04
 * 编译：g++ -o single_download single_download.cpp -lcurl
 * 执行: ./single_download https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso.zsync ubuntu.zsync
 * 单线程下载优点：单线程不用加锁；mmap写不用加锁；易用稳定
 * 单线程下载缺点：基于curl，依赖curl；
 */
#include <bits/stdc++.h>
#include <curl/curl.h> // libcurl中的头文件
/**
 * Ubuntu中使用如下两条命令安装libcurl：
 * sudo apt update
 * sudo apt install libcurl4-openssl-dev
 */
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
using namespace std;

struct fileInfo
{
    char *fileptr;
    int offset;
};

// 循环调用函数，ptr应该在堆上调用，不然大文件和循环多次容易造成栈溢出
/**
 * 保存文件两种思路：
 * 1. fwrite/write
 *  write是系统调用；fwrite是接口，调用的write
 * 2. mmap
 *  可以直接创建文件，存内容，使用mmap
 */
size_t writeFunc(void *ptr, size_t size, size_t memb, void *userdata)
{
    // cout << "WriteFunc: " << size * memb << endl;
    struct fileInfo *info = (struct fileInfo *)userdata;
    // 按偏移写入数据
    memcpy(info->fileptr + info->offset, ptr, size * memb);
    info->offset += size * memb;
    return size * memb;
}

// 请求文件大小
double getDownloadFileLength(const char *url)
{
    double downloadFileLength = 0;
    // 初始化
    CURL *curl = curl_easy_init();
    // 只发送HTTP头部
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "MyCustomClient/1.0");
    curl_easy_setopt(curl, CURLOPT_HEADER, 1);
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1);

    // 执行网络连接，建立TCP连接，等待HTTP请求，等待数据返回，循环等待数据读取完毕
    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK)
    {
        curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLength);
    }
    else
    {
        downloadFileLength = -1;
    }
    // 清空上下文
    curl_easy_cleanup(curl);
    return downloadFileLength;
}

int download(const char *url, const char *filename)
{
    // 请求文件大小
    long fileLength = getDownloadFileLength(url);
    cout << "文件大小为：" << fileLength << endl;

    // 创建文件 需要头文件 #include <fcntl.h>
    // S_IRUSR | S_IWUSR 改变文件的权限
    int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1)
        return -1;
    // 开辟新的文件的大小
    if (lseek(fd, fileLength - 1, SEEK_SET) == -1)
    {
        perror("lseek");
        close(fd);
        return -1;
    }
    if (write(fd, "", 1) != 1)
    {
        perror("write");
        close(fd);
        return -1;
    }
    char *fileptr = (char *)mmap(NULL, fileLength, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (fileptr == MAP_FAILED)
    {
        perror("mmap");
        close(fd);
        return -1;
    }
    struct fileInfo *info = (struct fileInfo *)malloc(sizeof(struct fileInfo));
    // 分配失败，释放
    if (info == nullptr)
    {
        munmap(fileptr, fileLength);
        close(fd);
        return -1;
    }
    info->fileptr = fileptr;
    info->offset = 0;

    // 初始化
    CURL *curl = curl_easy_init();
    // 设置参数
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, info);
    // 执行网络连接，建立TCP连接，等待HTTP请求，等待数据返回，循环等待数据读取完毕
    // 这里对多线程不友好，因为这部分比较耗时
    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK)
    {
        cout << "res: " << res << endl;
    }
    // 清空上下文
    curl_easy_cleanup(curl);

    // 手动释放malloc分配的内存，关闭mmap，关闭fd
    free(info);
    munmap(fileptr, fileLength);
    close(fd);
    return 0;
}

// 执行：single_download 下载链接 保存的文件名
int main(int argc, const char *argv[])
{
    if (argc != 3)
    {
        cout << "请执行: single_download 下载链接 保存的文件名" << endl;
        return -1;
    }
    download(argv[1], argv[2]);
    return 0;
}